package sweezy.swf.tools.xml
{
	import flash.utils.ByteArray;
	import sweezy.core.reflect.IType;
	import sweezy.core.reflect.Reflection;
	import sweezy.core.utils.ByteArrayUtils;
	import sweezy.core.utils.StringUtils;
	import sweezy.swf.SWF;
	import sweezy.swf.abc.ABC;
	import sweezy.swf.structure.ABCData;

	public class XMLConverter
	{

		public static function swfToXml(swf:SWF, extractAction:Boolean = false):XML
		{
			return new XML(swfToXmlString(swf, extractAction));
		}

		public static function swfToXmlString(swf:SWF, extractAction:Boolean = false):String
		{
			var context:SerializeContext = new SerializeContext();
			context.buf = "";
			context.extractAction = extractAction;

			writeObject(swf, context);
			return context.buf;
		}

		public static function xmlStringToSwf(xmlString:String):SWF
		{
			return xmlToSwf(new XML(xmlString));
		}

		public static function xmlToSwf(xml:XML):SWF
		{
			var swf:SWF = readObject(xml) as SWF;
			return swf;
		}

		private static function readABCData(node:XML):ABCData
		{
			var abcData:ABCData = new ABCData();
			var instance:* = readObject(node.data.*[0]);

			if (instance is ABC)
			{
				var abc:ABC = instance as ABC;
				abcData.data = abc.encode();
			}
			else if (instance is ByteArray)
			{
				abcData.data = instance;
			}

			return abcData;
		}

		private static function readArray(node:XML):Array
		{
			var result:Array = [];

			if (node !== null)
			{
				for each (var child:XML in node.*)
				{
					result[result.length] = readObject(child);
				}
			}

			return result;
		}

		private static function readByteArray(node:XML):ByteArray
		{
			return ByteArrayUtils.decodeBase64(node.text());
		}

		private static function readObject(node:XML):Object
		{
			if (node === null)
			{
				return null;
			}

			var name:String = node.name();
			var type:Class = TypeMappings.mappings[name];
			if (type === null)
			{
				return null;
			}

			var instance:Object;
			var scalar:Boolean = false;
			if (type === Array)
			{
				instance = readArray(node);
			}
			else if (type === ABCData)
			{
				instance = readABCData(node);
			}
			else if (type === ByteArray)
			{
				instance = readByteArray(node);
			}
			else
			{
				if (type in TypeMappings.unknownTypes)
				{
					instance = new type(node.@unknownCode);
				}
				else if (type in TypeMappings.scalarTypes)
				{
					if (type === Boolean)
					{
						instance = String(node.text()) === "true";
					}
					else if (type === String)
					{
						instance = String(node.text());
					}
					else if (type === int)
					{
						instance = int(node.text());
					}
					else if (type === uint)
					{
						instance = uint(node.text());
					}
					else
					{
						instance = Number(node.text());
					}

					return instance;
				}
				else
				{
					instance = new type();
				}

				var propertyMap:Object = TypeMappings.propertiesMaps[Reflection.forClass(type)];
				for each (var attribute:XML in node.attributes())
				{
					var propertyName:String = attribute.name();
					var prop:Property = propertyMap[propertyName];
					var propertyType:Class = prop.type;

					if (propertyType === String)
					{
						instance[propertyName] = String(attribute);
					}
					else if (propertyType === uint)
					{
						instance[propertyName] = uint(attribute);
					}
					else if (propertyType === int)
					{
						instance[propertyName] = int(attribute);
					}
					else if (propertyType === Boolean)
					{
						instance[propertyName] = String(attribute) === "true";
					}
					else if (propertyType === Number)
					{
						instance[propertyName] = Number(attribute);
					}
				}

				for each (var property:XML in node.children())
				{
					instance[String(property.name())] = readObject(property.children()[0]);
				}
			}

			return instance;
		}

		private static function writeABC(bytes:ByteArray, context:SerializeContext):void
		{
			var abc:ABC = new ABC();
			abc.decode(bytes);
			writeObject(abc, context);
		}

		private static function writeArray(array:Array, context:SerializeContext):void
		{
			context.buf += "<Array>";

			for (var i:uint = 0, len:uint = array.length; i < len; i++)
			{
				var value:* = array[i];
				if (value === null || value === undefined)
				{
					context.buf += "<Null/>";
					continue;
				}

				var typeName:String = (value is String) ? "String" : (value is int) ? "int" : (value is uint) ? "uint" : (value is Boolean) ? "Boolean" : (value is Number) ? "Number" : null;
				if (typeName !== null)
				{
					context.buf += "<";
					context.buf += typeName;
					context.buf += ">";
					context.buf += value is String ? StringUtils.escapeXMLString(value) : String(value);
					context.buf += "</";
					context.buf += typeName;
					context.buf += ">";
					continue;
				}

				writeObject(value, context);
			}

			context.buf += "</Array>";
		}

		private static function writeByteArray(bytes:ByteArray, context:SerializeContext):void
		{
			context.buf += "<ByteArray>";
			context.buf += ByteArrayUtils.encodeBase64(bytes);
			context.buf += "</ByteArray>";
		}

		private static function writeObject(object:*, context:SerializeContext):void
		{
			var type:IType = Reflection.forInstance(object);
			var name:String = type.shortName;
			var property:Property;
			var propertyValue:*

			if (!(name in TypeMappings.mappings))
			{
				return;
			}

			context.buf += "<";
			context.buf += name;
			context.buf += " ";
			for each (property in TypeMappings.propertiesLists[type])
			{
				if (property.scalar)
				{
					propertyValue = object[property.name];
					if (propertyValue !== null)
					{
						context.buf += property.name;
						context.buf += "=\"";
						context.buf += propertyValue is String ? StringUtils.escapeXMLString(propertyValue) : String(propertyValue);
						context.buf += "\" ";
					}
				}
			}
			context.buf += ">";

			for each (property in TypeMappings.propertiesLists[type])
			{
				if (property.scalar)
				{
					continue;
				}

				propertyValue = object[property.name];
				if (propertyValue === null)
				{
					continue;
				}

				context.buf += "<";
				context.buf += property.name;
				context.buf += ">";

				if (property.type === Array)
				{
					writeArray(propertyValue as Array, context);
				}
				else if (property.type === ByteArray)
				{
					if (object is ABCData && context.extractAction)
					{
						writeABC(propertyValue as ByteArray, context);
					}
					else
					{
						writeByteArray(propertyValue as ByteArray, context);
					}
				}
				else
				{
					writeObject(propertyValue, context);
				}

				context.buf += "</";
				context.buf += property.name;
				context.buf += ">";
			}

			context.buf += "</";
			context.buf += name;
			context.buf += ">";
		}
	}
}

class SerializeContext
{

	public var buf:String;

	public var extractAction:Boolean;
}
